查看原文
其他

红外遥控系统原理及单片机软件解码程序,我的编写经历(C版本)

mhjerry 芯片之家 2021-01-31

应该说现在每一块开发板都带有红外模块,并且大都配置了相应的程序。但其实自己动手写解码程序,更能锻炼自己所学,且不谈程序写的如何,这个过程中肯定是受益良多的。现在我就把我花一下午写出的解码程序与大家分享,期待高手的光临指正

首先,必须要了解一些基本原理。其实按下遥控器的某一个键,遥控器会发出一连串经过调制后的信号,这个信号经过红外一体化模块接收后,输出解调后的数字脉冲,每个按键对应不同的脉冲,故识别出不同的脉冲就能识别出不同的按键。

上图就是很常见的车载MP3遥控器,比较小巧,很好用。下面是红外发射和接受原理:

到此读者可能会有疑惑,那么不同的调制解调方法那么出来的脉冲规则是不一样的?是的,的确如此。

遥控发射器专用芯片很多,根据编码格式可以分成两大类,这里我们以运用比较广泛,解码比较容易的一类来加以说明,现以日本NEC的uPD6121G组成发射电路为例说明编码原理(一般家庭用的DVD、VCD、音响都使用这种编码方式)。当发射器按键按下后,即有遥控码发出,所按的键不同遥控编码也不同。这种遥控码具有以下特征:

       采用脉宽调制的串行码,以脉宽为0.565ms、间隔0.56ms、周期为1.125ms的组合表示二进制的“0”;以脉宽为0.565ms、间隔1.685ms、周期为2.25ms的组合表示二进制的“1”,其波形如图所示。

如图可见,0与1前端的低电平持续都是0.56ms,那么就是后面的高电平持续时间不同,0为0.56ms,1为1.685ms,找到不同之处,编程时就有识别的依据了!

上述“0”和“1”组成的32位二进制码经38kHz的载频进行二次调制以提高发射效率,达到降低电源功耗的目的。然后再通过红外发射二极管产生红外线向空间发射,如图所示。

UPD6121G产生的遥控编码是连续的32位二进制码组,其中前16位为用户识别码,能区别不同的电器设备,防止不同机种遥控码互相干扰。该芯片的用户识别码固定为十六进制01H;后16位为8位操作码(功能码)及其反码。UPD6121G最多额128种不同组合的编码。

请看下图,来自网络:


当一个键按下超过36ms,振荡器使芯片激活,将发射一组108ms的编码脉冲,这108ms发射代码由一个引导码(9ms),一个结果码(4.5ms),低8位地址码(9ms~18ms),高8位地址码(9ms~18ms),8位数据码(9ms~18ms)和这8位数据的反码(9ms~18ms)组成。如果键按下超过108ms仍未松开,接下来发射的代码(连发码)将仅由起始码(9ms)和结束码(2.25ms)组成。(实际上人手的动作是很慢的,即使你快速的按下按键,可能对于芯片来说还是超过108ms,所以如何处理连发码是很关键的)

遥控器在按键按下后,周期性地发出同一种32位二进制码,周期约为108ms。一组码本身的持续时间随它包含的二进制“0”和“1”的个数不同而不同,大约在45~63ms之间,图为发射波形图。

下面是我写的代码,按键编码通过串口发送到电脑端:

由于时间关系,代码注释不多。

其中START_Judge()函数是判断9ms低电平,既是判断有无遥控信号。

BOOT_REPEATING_CODE_Judge()是判断是引导码还是连发码,引导码则进入接受数据环节,连发码表明数据已经接受结束。

H_L_LEVEL_Judge()是接受数据时判断高低电平。


注明:以下代码为纯软件方式,没有用到中断,定时器方式,纯CPU查询,但测试结果倒也可以,至少比较稳定,得到的码值不管对不对,都是那个值。

  1. /*--------------------------------------------------

  2.  红外收发.C

  3.  遥控器测试

  4. ------------------------------------------------*/

  5. #include <reg52.h>

  6. // --- 红外接收一体化输出口

  7. sbit IR_Out = P3^2;

  8. bit START_Flag = 0;

  9. bit BOOT_REPEATING_CODE_Flag = 0;

  10. unsigned char DATA[4] = {0};

  11. bdata unsigned char TEMP_BIT;

  12. sbit B0 = TEMP_BIT^0;

  13. sbit B1 = TEMP_BIT^1;

  14. sbit B2 = TEMP_BIT^2;

  15. sbit B3 = TEMP_BIT^3;

  16. sbit B4 = TEMP_BIT^4;

  17. sbit B5 = TEMP_BIT^5;

  18. sbit B6 = TEMP_BIT^6;

  19. sbit B7 = TEMP_BIT^7;

  20. // --- 有无遥控信号判断函数

  21. bit START_Judge();

  22. // --- 连发码判断函数

  23. bit BOOT_REPEATING_CODE_Judge();

  24. // --- "0"和"1"识别

  25. bit H_L_LEVEL_Judge();

  26. // --- 串口初始化

  27. void UART_Initial();

  28. void DELAY_Us(unsigned int Us)

  29. {

  30.    unsigned int x;

  31.    for(x = 0; x <= (Us/200-1); x++);

  32. }

  33. void DELAY_Ms(unsigned int Ms)

  34. {

  35.    unsigned int x,y;

  36.    for(x = 0; x <= (Ms-1); x++)

  37.    {

  38.        for(y = 0; y <= 120; y++);

  39.    }

  40. }

  41. void main()

  42. {

  43.    unsigned char i;

  44.    UART_Initial();

  45.    IR_Out = 1;

  46.    while(1)

  47.    {      

  48.        START_Flag = START_Judge();

  49.        BOOT_REPEATING_CODE_Flag = BOOT_REPEATING_CODE_Judge();

  50.        if ( START_Flag && !BOOT_REPEATING_CODE_Flag )

  51.        {          

  52.            for(i =0;i <4; i++)

  53.            {              

  54.                B0 = H_L_LEVEL_Judge();

  55.                B1 = H_L_LEVEL_Judge();

  56.                B2 = H_L_LEVEL_Judge();

  57.                B3 = H_L_LEVEL_Judge();

  58.                B4 = H_L_LEVEL_Judge();

  59.                B5 = H_L_LEVEL_Judge();

  60.                B6 = H_L_LEVEL_Judge();

  61.                B7 = H_L_LEVEL_Judge();                            

  62.                DATA[i] = TEMP_BIT;

  63.            }

  64.            for(i =0;i <4; i++)

  65.            {

  66.                SBUF = DATA[i];

  67.                while( TI == 0 );

  68.                TI = 0;

  69.            }

  70.        }

  71.    }

  72. }

  73. void UART_Initial()

  74. {

  75.    SCON = 0x50;            // SCON: 模式 1, 8-bit UART, 使能接收

  76.    TMOD |= 0x20;           // TMOD: timer 1, mode 2, 8-bit reload

  77.    TH1 = 0xFD;             // TH1: reload value for 9600 baud @

  78.                            // 11.0592MHz

  79.    TR1 = 1;                // TR1: timer 1 run

  80.    EA = 0;                 // 关闭总中断

  81.    ES = 0;                 // 关闭串口中断

  82. }

  83. bit START_Judge()

  84. {

  85.    bit TEMP_Flag = 1;

  86.    unsigned char i = 0;

  87.    //在正常无遥控信号时,一体化红外接收头输出是高电平,程序一直在循环。

  88.    while ( IR_Out == 1);

  89.    //重复10次,目的是检测在6876~8352微秒内如果出现高电平就退出解码程序

  90.    for(i =0;i <9; i++)

  91.    {

  92.        DELAY_Us(800);      // 测试实际延时约为764~928us

  93.        if ( IR_Out == 1 )

  94.        {

  95.            TEMP_Flag = 0;

  96.            break;

  97.        }

  98.    }

  99.    return TEMP_Flag;

  100. }

  101. bit BOOT_REPEATING_CODE_Judge()

  102. {

  103.    bit TEMP_Flag = 1;

  104.    while( IR_Out == 0 ) ;  // 等待高电平避开9毫秒低电平引导脉冲

  105.    DELAY_Ms(1);            // 测试实际延时约为1.007ms

  106.    DELAY_Ms(1);            // 测试实际延时约为1.007ms

  107.    DELAY_Us(200);          // 0.086ms

  108.    DELAY_Us(200);          // 0.086ms  

  109.    DELAY_Us(200);          // 0.086ms

  110.                            // 共计2.272ms    

  111.    if( IR_Out == 0 )

  112.    {

  113.        TEMP_Flag = 1;      // 是连发码

  114.    }

  115.    else

  116.    {

  117.        TEMP_Flag = 0;      // 不是连发码,而是引导码

  118.    }

  119.    return TEMP_Flag;

  120. }

  121. bit H_L_LEVEL_Judge()

  122. {

  123.    while( IR_Out == 0 );   // 等待地址码第一位的高电平信号

  124.    DELAY_Us(800);          // 测试实际延时约为764~928us

  125.    if ( IR_Out == 1)

  126.    {

  127.        DELAY_Ms(1);        // 测试实际延时约为1.007ms

  128.        return 1;

  129.    }

  130.    else

  131.    {

  132.        return 0;

  133.    }

  134. }

编辑如下:

01 FE 8B 74 --- 01 FE 8D 72 --- 01 FE 8F 70

01 FE 89 76 --- 01 FE 81 7E --- 01 FE 87 78

01 FE 0F F0 --- 01 FE 2B D4 --- 01 FE 13 EC

01 FE 2D D2 --- 01 FE 33 CC --- 01 FE 1B E4

01 FE 19 E6 --- 01 FE 31 CE --- 01 FE BD 42

01 FE 11 EE --- 01 FE 39 C6 --- 01 FE B5 4A
以上为对应按键的编码。

过程中存在问题:

一是如何有效的识别引导码和连发码,因为这个能直接影响到长时间按键,单片机的响应与否。这个问题,貌似我以解决,就是长时间按键后,单片机识别一次按键后,如果还是同一按键,就不与理睬。

还有一个问题就是,如果连续按下两次按键,该程序能够识别出,但是如果间隔很短,第二下按键的编码容易出错,容易变成这样:

03 FE 8B 74.。。。就是第一个字节出现误差,这个问题现在还未来得及解决。

还有就是本程序对于延时函数的精度要求很高,因为本身处理的脉冲就是MS级别的。所以需要严格的测试延时函数的实际延时时间:

 以上的代码,可以看出许多问题,软件延时不准确,大量的“while( IR_Out == 0 ) ;”代码,抗干扰能力弱,容易进入死循环。

下面介绍的这种解码方法,利用外部中断触发程序,定时器定时(但没有设置定时中断程序,即判断TF的值确定定时结束),在代码过程中,开头的一个7.93ms延时,足以滤掉不合法的红外信号。应该说效率质量更高的。

代码注释很详细,在此不在细述:

  1. /*--------------------------------------------------

  2. 名称:遥控器红外解码,PO口接LED,显示功能码以供查看

  3. 编写:mhjerry

  4. 日期:2017.7

  5. 内容:按遥控器上的按键,会在PO口LED上显示

  6. --------------------------------------------------*/

  7. #include "reg52.h"

  8. sbit IR_Out = P3^2;    // 此口为红外信号输入MCU口

  9. sbit IR_Flag = P3^1;  // 主程序运行标志位,运行主程序时LED灭,运行中断程序时LED亮    

  10. #define LED_Port P1  // LED显示口

  11. unsigned char dat[4] = {0,0,0,0};  // 用于存放按键码值,初始化为0000 0000这样接受数据时可以只考虑1了    

  12. void main()

  13. {

  14.    IR_Out = 1;     // 此口为MCU输入口,故需要置1

  15.    IR_Flag = 1;    // 灭LED灯

  16.    TMOD = 0x01;    // 定时器0,方式1

  17.    IT0 = 1;        // 外部中断0,下降沿触发

  18.    EX0 = 1;        // 准许外部中断

  19.    EA = 1;         // CPU准许中断

  20.    while(1)

  21.    {

  22.        IR_Flag = 1;// 执行主程序时,LED灯灭

  23.    }

  24. }

  25. /*------------------------------------------------------------*-

  26.  函数名称:Int0()

  27.  函数输入:无(容许中断时,外部触发)

  28.  函数输出:无

  29.  函数说明:外部中断0中断处理

  30. -*------------------------------------------------------------*/

  31. void Int0() interrupt 0

  32. {

  33.    unsigned char i,j;

  34.    EX0 = 0;            // 关闭外部中断0

  35.    IR_Flag = 0;        // 执行中断程序时,LED灯亮

  36.    i = 10;             // 0.793ms延时,运行10次

  37.    while( --i )

  38.    {

  39.        // 定时0.793ms,延时0.793ms*10=7.93ms

  40.        TH0 = 0xfc;

  41.        TL0 = 0xe7;

  42.        TR0 = 1;

  43.        while( !TF0 );

  44.        TF0 = 0;

  45.        TR0 = 0;

  46.        // 这7.93ms期间只要IR_Out变高电平,就非合法的红外信号,跳出

  47.        if( IR_Out )

  48.        {

  49.            EX0 = 1;    // 准许中断

  50.            return ;

  51.        }

  52.    }

  53.    // 程序进行到这里,表明是合法的红外信号(利用9ms判断)

  54.    while( !IR_Out );   // 等待9ms低电平过去

  55.    // 程序进行到这里,表明经过9ms低电平

  56.    TH0 = 0xf6;

  57.    TL0 = 0xff;

  58.    TR0 = 1;

  59.    while( !TF0 );

  60.    TF0 = 0;

  61.    TR0 = 0;            // 延时2.305ms

  62.    // IR_Out 为低表明是连发码,不予理睬,跳出

  63.    if( !IR_Out )

  64.    {

  65.        EX0=1;

  66.        return;

  67.    }

  68.    // 程序进行到这里,表明是引导码,等待4.5ms高电平的过去

  69.    while( IR_Out );    

  70.    // 开始接收用户码

  71.    for(i=0; i<4; i++)

  72.     {

  73.        for(j=0; j<8; j++)

  74.        {

  75.            while( !IR_Out );   // 等待低电平过去

  76.               dat[i] >>= 1;       // 把上次的数据位右移一位

  77.            TH0 = 0xfc;

  78.            TL0 = 0xe7;

  79.            TR0 = 1;

  80.            while( !TF0 );

  81.            TR0=0;

  82.            TF0=0;              //延时0.793ms

  83.            // 若为数据"1",则延时后IR_Out为高电平

  84.            if( IR_Out )

  85.            {

  86.                dat[i] |= 0x80;     // 所有数据位1放最高位

  87.                while( IR_Out );    // 等待高电平过去

  88.            }

  89.        }

  90.      }  

  91.    LED_Port = dat[2];

  92.     EX0=1;      // 开中断

  93.    return;

  94. }

往期好文

别用代码逼死你的队友


MPS软件帮您搞定DC-DC电源设计!


器件为什么只听英文Datasheet的话


全套中文Keil MDK视频学习资料免费公开


AD PCB-如何设置差分对与开发困境说No!


抄袭需谨慎:深圳公司“反向设计芯片“ 法人获刑三年

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存